package impl;

import interfaces.RobotInfo;

import java.util.ListIterator;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import candySystemNamespace.Robot;
import classes.Constants;
import classes.Coordinate;
import classes.ObservableList;
import classes.Utils;

/**
 * Implementation of the Abstract class defined in SpeADL. Characteristics:
 * <ul>
 * 
 * <li>The robot will be instantiated at a certain position, and then, passing by the 4 states (Thinking, Deciding, Acting, Death),
 * it will communicate with the Map and try to crab the candies.</li>
 * 
 * <li>It is allowed to move only in the 4 directions Up, Down, Left, Right</li>
 * 
 * <li>When he is trying to search for candies, he will be constantly going Left</li>
 * 
 * <li>When he wants to retrieve the candy, he will be constantly going Right</li>
 * 
 * </ul>
 */
public class RobotImpl extends Robot {
	private final String TAG = Constants.TAG + "." + getClass().getSimpleName();
	
	/** The time interval (in seconds) at which the acting runnable will be executed */
	private final int ACTING_INTERVAL = 100; // 1 second
	
	/** The state in which the Robot retrieves the free positions */
	public static final int STATE_THINKING = 0;
	
	/** The state in which the Robot decides which position to occupy */
	public static final int STATE_DECIDING = 1;
	
	/** The state in which the Robot takes a new position */
	public static final int STATE_ACTING = 2;
	
	/** State with which to notify the world that robot is dead */
	public static final int STATE_DEAD = 3;
	
	/** State with which to notify the world that robot has killed himself */
	public static final int STATE_KILLED_SELF = 4;
	
	/** Variable used for showing if the Robot is carrying a candy or not */
	private boolean hasCandy = false;
	
	/** The current position of the robot */
	private Coordinate currentPosition;
	
	/** The position where the robot wants to move, but first he must gain access from the Map */
	private Coordinate wantedPosition;
	
	/** The temporary list of free positions */
	private ObservableList<Coordinate> freePositions;
	
	/** The temporary list of candy positions */
	private ObservableList<Coordinate> candiesPosition;
	
	/** The current state of the Robot */
	private int state = STATE_THINKING;
	
	/** Variable for the robot's living state */
	private boolean isAlive = false;
	
	/** Scheduler for acting threads */
	private static final ScheduledExecutorService worker = Executors.newSingleThreadScheduledExecutor();
	
	private ScheduledFuture<?> scheduledFuture;
	
	private Runnable actRunnable = new Runnable() {
		
		@Override
		public void run() { 
			if (!isAlive) {
				if (scheduledFuture != null)
					scheduledFuture.cancel(true);
				
				return;
			}
			
			if (state == STATE_THINKING) {
				freePositions = requires().mapPositions().getFreePositions();
				candiesPosition = requires().mapPositions().getCandiesPositions();
				state = STATE_DECIDING;
			} else if (state == STATE_DECIDING) {
				if (hasCandy) {
					// Try to go to base - Right
					ListIterator<Coordinate> itr = freePositions.listIterator();
					while (itr.hasNext()) {
						Coordinate free = itr.next();
						if (free.getY() == currentPosition.getY() && free.getX() - currentPosition.getX() == 1) {
							// Move right
							wantedPosition = free;
							state = STATE_ACTING;
							return;
						}
					}
					
					// In case it could not go right, try to go Up or Down
					itr = freePositions.listIterator();
					while (itr.hasNext()) {
						Coordinate free = itr.next();
						if (Math.abs(free.getY() - currentPosition.getY()) == 1 && free.getX() == currentPosition.getX()) {
							wantedPosition = free;
							state = STATE_ACTING;
							return;
						}
					}
					
					// If it could not go Right, Up, Down
					die(-1, -1);
				} else {
					// Try to grab a candy
					// Check if there are any candies near by
					ListIterator<Coordinate> itr = candiesPosition.listIterator();
					while (itr.hasNext()) {
						Coordinate candy = itr.next();
						int x_c = candy.getX();
						int y_c = candy.getY();
						int x_r = currentPosition.getX();
						int y_r = currentPosition.getY();
						
						if (Math.sqrt((x_r - x_c) * (x_r - x_c) + (y_r - y_c) * (y_r - y_c)) < 3) {
							// Find a free place to move in the direction of the candy
							if (x_r - x_c > 0)
								wantedPosition = new Coordinate(currentPosition.getX() - 1, currentPosition.getY());
							else
								wantedPosition = new Coordinate(currentPosition.getX(), currentPosition.getY() + (y_r - y_c > 0 ? -1 : 1));
							
							state = STATE_ACTING;
							return;
						}
					}
					
					// There are no candies near by - move in the direction of the candy deposit -> Left
					ListIterator<Coordinate> itrFree = freePositions.listIterator();
					while (itrFree.hasNext()) {
						Coordinate free = itrFree.next();
						if (free.getY() == currentPosition.getY() && currentPosition.getX() - free.getX() == 1) {
							// Move Left
							wantedPosition = free;
							state = STATE_ACTING;
							return;
						}
					}
					
					itrFree = freePositions.listIterator();
					while (itrFree.hasNext()) {
						Coordinate free = itrFree.next();
						if (Math.abs(free.getY() - currentPosition.getY()) == 1 && free.getX() == currentPosition.getX()) {
							// Move Up or down
							wantedPosition = free;
							state = STATE_ACTING;
							return;
						}
					}
					
					// There is no place to go
					die(0, 0);
				}
			} else if (state == STATE_ACTING) {
				if (wantedPosition == null) {
					// Something went wrong - kill self
					die(0, 0);
				}
				
				if (requires().mapPositions().getFreePositions().contains(wantedPosition)) {
					if (requires().mapPositions().getFreePositions().remove(wantedPosition)) {
						requires().mapPositions().getFreePositions().add(currentPosition);
						currentPosition = wantedPosition;
						wantedPosition = null;
					} else {
						// Failed to grab the new position
						die(0, 0);
						return;
					}
				} else if (requires().mapPositions().getCandiesPositions().contains(wantedPosition)) {
					if (requires().mapPositions().getCandiesPositions().remove(wantedPosition)) {
						hasCandy = true;
						requires().mapPositions().getFreePositions().add(currentPosition);
						currentPosition = wantedPosition;
						wantedPosition = null;
					} else {
						// Failed to grab candy
						die(1, 1);
						return;
					}
				} else {
					// The wanted position is no longer available
					die(0, 0);
					return;
				}
				
				state = STATE_THINKING;
			}
		}
	};
	
	public RobotImpl(int x, int y) {
		currentPosition = new Coordinate(x , y);
		isAlive = true;
	}
	
	@Override
	protected void start() {
		super.start();
		
		// Randomize start time
		int delay = Math.abs(new Random().nextInt()) % 10;
		
		scheduledFuture = worker.scheduleAtFixedRate(actRunnable, delay, ACTING_INTERVAL, TimeUnit.MILLISECONDS);
	}
	
	/** Method called in order to destroy the robot. It can be called by himself or by the map (if his current position is destroyed) */
	public void die(int deltaX, int deltaY) {
		Utils.logC(TAG, "Dying: " + currentPosition.getX() + " | " + currentPosition.getY());
		
		
		if (hasCandy) {
			// If it has candy, leave it on the map
			requires().mapPositions().getCandiesPositions().add(currentPosition);
		} else {
			// Set its location as free
			requires().mapPositions().getFreePositions().add(currentPosition);
		}
		
		isAlive = false;
		hasCandy = false;
		state = STATE_DEAD;

		// Create another robot
		if (deltaX != 0 || deltaY != 0) {
			Coordinate newPosition = new Coordinate(currentPosition.getX() + deltaX, currentPosition.getY() + deltaY);
			if (requires().mapPositions().getFreePositions().contains(newPosition))
				currentPosition = newPosition;
			else
				currentPosition = new Coordinate(0, 0);
			state = STATE_KILLED_SELF;
			Utils.logC(TAG, "Robot killed self. New position: " + currentPosition.getX() + " | " + currentPosition.getY());
		}
	}
	
	@Override
	protected RobotInfo make_info() {
		return new RobotInfo() {
			public boolean hasCandy() {
				return hasCandy;
			}
			
			public int getState() {
				return state;
			}
			
			public Coordinate getPosition() {
				return currentPosition;
			}
		};
	}
}
